Skip to content

Replace popen('uname -ap') with uname() syscall in bvar kernel_version - #3517

Merged
yanglimingcn merged 10 commits into
apache:masterfrom
weim0000:fix_uname
Sep 9, 2026
Merged

yanglimingcn merged 10 commits into
apache:masterfrom
weim0000:fix_uname

Conversation

@weim0000

@weim0000 weim0000 commented Sep 3, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Problem Summary:

When a brpc server has allocated a large amount of memory, the first request to the /vars endpoint can cause a significant latency stall. This is because the kernel_version bvar variable is lazily initialized on first access, and its constructor calls popen("uname -ap") to read the kernel version.

Internally, popen() calls fork() to spawn a child process. On Linux, fork() needs to duplicate the parent process's page tables. For a server with a large memory footprint (e.g., tens of GBs), this can take hundreds of milliseconds or even longer, effectively blocking the bthread that handles the /vars request.

This caused a production incident in our environment, where the service appeared to hang when the monitoring system first scraped the /vars endpoint after the server had been running for a while with heavy memory usage.

What is changed and the side effects?

Changed:

Replace butil::read_command_output(oss, "uname -ap") (which shells out via popen → fork → exec) with the POSIX uname() syscall in src/bvar/default_variables.cpp. The uname() syscall reads the same kernel information directly via struct utsname, without creating any child process. The output format remains equivalent to uname -ap. A unit test is added in test/bvar_variable_unittest.cpp.

Side effects:

  • Performance effects: Eliminates the fork() overhead entirely. The uname() syscall completes in microseconds regardless of the process's memory usage, whereas the previous popen() approach could stall for 100ms+ on large-memory processes.

  • Breaking backward compatibility: No. The output format of the kernel_version bvar remains the same.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new kernel_version formatting is observably different from uname -ap (newline + processor field) and the added test introduces a missing-header build risk while not exercising the actual bvar path end-to-end.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR removes the use of popen("uname -ap") (and its fork() overhead) from the lazy initialization of the kernel_version bvar by switching to the uname() syscall, aiming to eliminate first-hit /vars latency spikes in large-memory processes.

Changes:

  • Replace butil::read_command_output(..., "uname -ap") with uname()-based string construction for kernel_version.
  • Add a unit test intended to validate the uname()-derived kernel info formatting.
File summaries
File Description
src/bvar/default_variables.cpp Switch kernel_version collection from shelling out to uname() syscall.
test/bvar_variable_unittest.cpp Add a new test around uname() output/format assumptions.
Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/bvar/default_variables.cpp Outdated
Comment thread test/bvar_variable_unittest.cpp Outdated
Comment on lines 20 to 23
#include <pthread.h> // pthread_*
#include <unistd.h> // usleep
#include <sys/utsname.h> // uname

Comment thread test/bvar_variable_unittest.cpp Outdated
Comment on lines +467 to +483
TEST_F(VariableTest, uname_returns_valid_kernel_info) {
struct utsname buf;
ASSERT_EQ(0, uname(&buf));

// Each field should be non-empty
ASSERT_GT(strlen(buf.sysname), 0u);
ASSERT_GT(strlen(buf.nodename), 0u);
ASSERT_GT(strlen(buf.release), 0u);
ASSERT_GT(strlen(buf.version), 0u);
ASSERT_GT(strlen(buf.machine), 0u);

// Build the string the same way ReadVersion does in default_variables.cpp
std::ostringstream oss;
oss << buf.sysname << ' ' << buf.nodename << ' '
<< buf.release << ' ' << buf.version << ' '
<< buf.machine << ' ' << buf.machine;
std::string content = oss.str();
weim0000 and others added 3 commits September 5, 2026 20:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new kernel_version formatting is not actually equivalent to uname -ap on Linux and the added test doesn’t currently assert the behavior of the kernel_version bvar itself.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

test/bvar_variable_unittest.cpp:494

  • This new test validates that uname() returns non-empty fields, but it does not assert that the kernel_version bvar actually uses this formatting (or that its output stayed stable). Consider asserting bvar::Variable::describe_exposed("kernel_version") matches the expected string built from utsname so the test covers the behavior introduced by this PR.
    std::string content = oss.str();

    // The result should contain all key fields
    ASSERT_NE(content.find(buf.sysname), std::string::npos);
    ASSERT_NE(content.find(buf.release), std::string::npos);
  • Files reviewed: 2/2 changed files
  • Comments generated: 1
  • Review effort level: Lite

Comment thread src/bvar/default_variables.cpp Outdated
Comment on lines +621 to +636
struct utsname buf;
if (uname(&buf) != 0) {
LOG(ERROR) << "Fail to read kernel version";
return;
}
#if defined(__APPLE__) && (defined(__aarch64__) || defined(__arm64__))
const char* processor = "arm";
#elif defined(__APPLE__) && defined(__x86_64__)
const char* processor = "i386";
#else
const char* processor = buf.machine;
#endif
std::ostringstream oss;
oss << buf.sysname << ' ' << buf.nodename << ' '
<< buf.release << ' ' << buf.version << ' '
<< buf.machine << ' ' << processor << '\n';

@wwbmmm wwbmmm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good fix that removes the fork()/popen stall on the /vars hot path; however the output is not strictly equivalent to uname -ap, so the no-compatibility-break claim needs to be corrected or the format adjusted.


🤖 This reply was automatically generated by brpc-oncall

Comment thread src/bvar/default_variables.cpp Outdated
Comment thread src/bvar/default_variables.cpp Outdated
@wwbmmm
wwbmmm requested a lite review from Copilot September 7, 2026 09:16

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.

Comment thread src/bvar/default_variables.cpp Outdated
Comment on lines +621 to 625
struct utsname buf;
if (uname(&buf) != 0) {
LOG(ERROR) << "Fail to read kernel version";
return;
}
Comment thread test/bvar_variable_unittest.cpp Outdated
Comment on lines +467 to +469
TEST_F(VariableTest, uname_returns_valid_kernel_info) {
struct utsname buf;
ASSERT_EQ(0, uname(&buf));
Comment thread test/bvar_variable_unittest.cpp Outdated
Comment on lines +478 to +493
// Build the string the same way ReadVersion does in default_variables.cpp
#if defined(__APPLE__) && (defined(__aarch64__) || defined(__arm64__))
const char* processor = "arm";
#elif defined(__APPLE__) && defined(__x86_64__)
const char* processor = "i386";
#else
const char* processor = buf.machine;
#endif
std::ostringstream oss;
oss << buf.sysname << ' ' << buf.nodename << ' '
<< buf.release << ' ' << buf.version << ' '
<< buf.machine << ' ' << processor;
#if !defined(__APPLE__)
oss << " GNU/Linux";
#endif
oss << '\n';
Comment thread test/bvar_variable_unittest.cpp Outdated
Comment thread test/bvar_variable_unittest.cpp Outdated
Comment on lines +472 to +476
ASSERT_GT(strlen(buf.sysname), 0u);
ASSERT_GT(strlen(buf.nodename), 0u);
ASSERT_GT(strlen(buf.release), 0u);
ASSERT_GT(strlen(buf.version), 0u);
ASSERT_GT(strlen(buf.machine), 0u);
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@wwbmmm
wwbmmm requested a lite review from Copilot September 7, 2026 09:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

Comment thread src/bvar/default_variables.cpp Outdated
oss << buf.sysname << ' ' << buf.nodename << ' '
<< buf.release << ' ' << buf.version << ' '
<< buf.machine << ' ' << processor;
#if !defined(__APPLE__)
Comment thread test/bvar_variable_unittest.cpp Outdated
Comment on lines +478 to +503
// Build the string the same way ReadVersion does in default_variables.cpp
#if defined(__APPLE__) && (defined(__aarch64__) || defined(__arm64__))
const char* processor = "arm";
#elif defined(__APPLE__) && defined(__x86_64__)
const char* processor = "i386";
#else
const char* processor = buf.machine;
#endif
std::ostringstream oss;
oss << buf.sysname << ' ' << buf.nodename << ' '
<< buf.release << ' ' << buf.version << ' '
<< buf.machine << ' ' << processor;
#if !defined(__APPLE__)
oss << " GNU/Linux";
#endif
oss << '\n';
std::string content = oss.str();

// The result should contain all key fields
ASSERT_NE(content.find(buf.sysname), std::string::npos);
ASSERT_NE(content.find(buf.release), std::string::npos);
ASSERT_NE(content.find(buf.machine), std::string::npos);

// On Linux, sysname should be "Linux"; on macOS, "Darwin"
#if defined(__linux__)
ASSERT_STREQ(buf.sysname, "Linux");
Comment on lines +621 to 625
struct utsname buf;
if (uname(&buf) != 0) {
LOG(ERROR) << "Fail to read kernel version";
return;
}
weim0000 and others added 2 commits September 7, 2026 19:25
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

There is at least one concrete build/correctness issue (missing header for strerror) and a compatibility-risky formatting decision that should be resolved/clarified before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 2/2 changed files
  • Comments generated: 3
  • Review effort level: Lite

#include <unistd.h> // getpagesize
#include <sys/types.h>
#include <sys/resource.h> // getrusage
#include <sys/utsname.h> // uname
Comment thread src/bvar/default_variables.cpp Outdated
Comment on lines +631 to +635
#else
const char* processor = buf.machine;
#endif
const char* hardware_platform = buf.machine;
std::ostringstream oss;
Comment thread test/bvar_variable_unittest.cpp Outdated
ASSERT_TRUE(destructed.load());
}

TEST_F(VariableTest, uname_returns_valid_kernel_info) {
@wwbmmm

wwbmmm commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

LGTM. The header-only make_kernel_version_string helper is shared by production and tests, the GNU/Linux suffix is correctly gated to Linux (non-Android), the failure log now carries errno, and my earlier unused-variable concern is resolved. No remaining blockers.


🤖 This reply was automatically generated by brpc-oncall

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new unit test’s Linux-only assertions will fail on Android builds and the newly introduced installed header/function expands the public API surface in a way that likely needs adjustment/confirmation.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment on lines +623 to 627
if (uname(&buf) != 0) {
LOG(ERROR) << "Fail to read kernel version, errno=" << errno
<< " (" << strerror(errno) << ")";
return;
}
Comment on lines +501 to +503
#if defined(__linux__)
ASSERT_STREQ(buf.sysname, "Linux");
ASSERT_NE(content.find("GNU/Linux"), std::string::npos);
// from unit-test binaries (see BVAR_NOT_LINK_DEFAULT_VARIABLES in
// variable.cpp), so keeping the formatting logic here lets tests exercise the
// exact production formatter without depending on that object being linked.
inline std::string make_kernel_version_string(const struct utsname& buf) {

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

There are verified correctness/portability issues in the new error logging (direct strerror() use) and in the unit test’s platform guards (Linux vs Android mismatch).

Review details

Suppressed comments (2)

src/bvar/default_variables.cpp:626

  • strerror(errno) is used here, but default_variables.cpp does not include <string.h>/<cstring>, and strerror() is also not thread-safe. Since this file already uses PLOG, prefer PLOG(ERROR) to log the errno message without relying on strerror() directly.
        struct utsname buf;
        if (uname(&buf) != 0) {
            LOG(ERROR) << "Fail to read kernel version, errno=" << errno
                       << " (" << strerror(errno) << ")";
            return;

test/bvar_variable_unittest.cpp:507

  • The test’s platform guard doesn’t match the production formatter: make_kernel_version_string appends " GNU/Linux" only when defined(__linux__) && !defined(__ANDROID__), but the test currently asserts that substring for all __linux__ builds. This will fail on Android toolchains where __linux__ is also defined.
    // On Linux, sysname is "Linux" and the OS suffix is appended; on macOS,
    // sysname is "Darwin" and there is no OS suffix (both match `uname -ap`).
#if defined(__linux__)
    ASSERT_STREQ(buf.sysname, "Linux");
    ASSERT_NE(content.find("GNU/Linux"), std::string::npos);
#elif defined(__APPLE__)
    ASSERT_STREQ(buf.sysname, "Darwin");
    ASSERT_EQ(content.find("GNU/Linux"), std::string::npos);
#endif
  • Files reviewed: 3/3 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@wwbmmm
wwbmmm requested a lite review from Copilot September 8, 2026 03:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wwbmmm
wwbmmm requested a lite review from Copilot September 8, 2026 03:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wwbmmm
wwbmmm requested a lite review from Copilot September 8, 2026 04:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@weim0000

weim0000 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

@wwbmmm copilot has triggered the limit

@wwbmmm
wwbmmm requested a lite review from Copilot September 8, 2026 05:40

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wwbmmm
wwbmmm requested a lite review from Copilot September 8, 2026 06:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wwbmmm
wwbmmm requested a lite review from Copilot September 8, 2026 08:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wwbmmm
wwbmmm requested a lite review from Copilot September 8, 2026 09:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wwbmmm
wwbmmm requested a lite review from Copilot September 8, 2026 10:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wwbmmm
wwbmmm requested a lite review from Copilot September 8, 2026 11:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@wwbmmm
wwbmmm requested a lite review from Copilot September 8, 2026 12:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@weim0000

weim0000 commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

@wwbmmm copilot has reached the limit

@wwbmmm

wwbmmm commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

@wwbmmm copilot has reached the limit

Sorry for the copilot spam, I have fixed it.

@wwbmmm

wwbmmm commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

LGTM
The CI failure seems unrelated to this PR.

@yanglimingcn

Copy link
Copy Markdown
Contributor

LGTM

@yanglimingcn
yanglimingcn merged commit 82ab78a into apache:master Sep 9, 2026
44 of 46 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants